/**
 * Copyright (c) 2023 Yin. All rights reserved.
*/

#ifndef _BROADCAST_H_
#define _BROADCAST_H_
#if defined(_WIN32) || defined(_WIN64)
#include <winsock2.h>   // 链接时需要添加 -lwsock32
#include <windows.h>
#include <ws2tcpip.h>   // -lws2_32
// #pragma comment(lib, "ws2_32.lib")
typedef unsigned long in_addr_t;
#else   /* defined(_WIN32) || defined(_WIN64)  */
#include <netinet/in.h>
// #include <asm-generic/socket.h>
#include <arpa/inet.h>
#endif  /* defined(_WIN32) || defined(_WIN64)  */
#include <stdbool.h>
#include <stdint.h>
#if BRO_USEING_MUTEX
#include <pthread.h>
#endif
#include "avllib.h"

/**
 * @brief version (main.sub.update)
 * @param main     huge change in software. 
 *                 it always means last version is not adapter to this version
 *                 like v1.x.x and v2.x.x
 * @param sub    big change in software
 *                 it still change a lot , but it can prefectly adapter last version
 *                 like v1.0.0 and v1.10.4 can work together
 * @param update update in software
 *                 fixing bug or add a new function
 * @note  each version up to 2 digits
 */
#define BROADCAST_VERSION "v1.1.4"

#ifdef __x86_64__
#define COMPLATE_MESSAGE "Compiled for 64-bit x86_64 architecture"
#elif defined(__i386__)
#define COMPLATE_MESSAGE "Compiled for 32-bit x86 architecture"
#elif defined(__aarch64__)
#define COMPLATE_MESSAGE "Compiled for 64-bit ARM64 (aarch64) architecture"
#elif defined(_WIN64)
#define COMPLATE_MESSAGE "Compiled for 64-bit Windows"
#elif defined(__arm__) && !defined(__aarch64__)
#define COMPLATE_MESSAGE "Compiled for 32-bit ARM architecture"
#else
#define COMPLATE_MESSAGE "Unknown architecture"
#endif

#ifdef __GNUC__     // GCC
#define DEPRECATED(fun, message)  fun __attribute__ ((deprecated(message)))
#elif defined(_MSC_VER)     // MSVC
#define DEPRECATED(fun, message)  __declspec(deprecated(message)) fun
#else
#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
#define DEPRECATED(fun, message)
#endif

typedef enum {
    B_TYPE_CLIENT = 0,
    B_TYPE_SERVER
} broadcast_type;

// server is master, client is slaver
typedef enum {
    B_MODE_MISO = 0,    // client to be the broadcaster (master is input, slaver is output)
    B_MODE_MOSI,        // server to be the broadcaster
} broadcast_mode;

struct pairing_set {
    bool enable;
    int code;
};

struct broadcast_remote_info {
    char remote_ip[16];         
    uint16_t remote_port;
    char name[48];
};
typedef int (*broadcastGetRemoteInfoCallback)(struct broadcast_remote_info* info, void *arg);

struct broadcast_handle {
    int fd;
    broadcast_type type;
    broadcast_mode mode;
    struct sockaddr_in thisAddr;
    long timeout;
    char msg[64];
    char version[12];
    int list_cnt;
    AVL_TREE remoteList;
    #if BRO_USEING_MUTEX
    pthread_mutex_t mutex;
    #endif
    bool (*verified)(char *recv, void *arg);
    void *verifyArg;
    char *userData;
    broadcastGetRemoteInfoCallback getInfoCallback;
    void *callbackArg;
    struct pairing_set paring_code;
    char name[48];
};

const char* broadcast_version(void);

/**
 * @brief get broadcast address by ip and mask
 * @param ip net adapter ip
 * @param mask net adapter mask
 * @return broadcast address
 * @note this is not thread safe, need to copy return immediately
*/
char *broadcast_get(char *ip, char *mask);

/**
 * @brief client/server init function
 * @param broadcast_handle broadcast handle
 * @param mode define the handle behavior, see @broadcast_mode 
 * @param ip for windows:
 *           if mode is output, ip is broadcast address, can using broadcast_get() to generate. 
 *           if mode is input, ip is your locol ip  
 *           eg: if mode == B_MODE_MOSI , broadcast_client_init(,,"locol ip",,);
 *                                        broadcast_server_init(,,"broadcast ip",,);
 *           for linux:
 *           ip is broadcast address
 * @param port client and server must set the same port
 * @param timeout after timeout seconds, broadcast_xxx_run() will return. set -1 will wait forever. but for output, is not a good idea. 
 * @return -1 for fail(get error information by broadcast_getErr()), 0 for success
 * @note do not try to nested function!!!
*/
DEPRECATED(int broadcast_client_init(struct broadcast_handle *bc, broadcast_mode mode, char *ip, uint16_t port, long timeout), "using broadcast_client_init2() instead");
int broadcast_client_init2(struct broadcast_handle *bc, broadcast_mode mode, char *local_ip, uint16_t port, long timeout);

/**
 * @note see broadcast_client_init()
 */
DEPRECATED(int broadcast_server_init(struct broadcast_handle *bc, broadcast_mode mode, char *ip, uint16_t port, long timeout), "using broadcast_server_init2() instead");
int broadcast_server_init2(struct broadcast_handle *bc, broadcast_mode mode, char *local_ip, uint16_t port, long timeout);

#ifdef __linux__
int bind_to_device(struct broadcast_handle *bc, char *name);
#endif  /* __linux__ */

/**
 * @brief start excute find other devices
 * @return -1 for fail(get error information by broadcast_getErr()), 0 for success
*/
int broadcast_client_run(struct broadcast_handle *bc);
int broadcast_server_run(struct broadcast_handle *bc);

/**
 * @brief get remote ip and port
 * @param bc 
 * @param cb support callback function by user. prototype: int (*broadcastGetRemoteInfoCallback)(char* ip, uint16_t port, void *arg);
 *           in this function, user can get remote ip and port. if the net environment have multiple clients, cb will be excuted multiple times
 * @param arg be arg in cb
 * @note  cb return 0 means no error. if cb return -1 ,this function will return immediately.
*/
int broadcast_get_remote_info(struct broadcast_handle *bc, broadcastGetRemoteInfoCallback cb, void *arg);

/**
 * @brief get error information
*/
char *broadcast_getErr(struct broadcast_handle *bc);

/**
 * @brief close and free resources
*/
int broadcast_close(struct broadcast_handle *bc);

/**
 * @brief Set the pairing code object
 * 
 * @param bc 
 * @param code 
 * @return int -1 for fail, 0 for success
 */
int set_pairing_code(struct broadcast_handle *bc, int code);

/**
 * @brief Set the user verify object
 * 
 * @param bc 
 * @param data user personal data. it's best to use string, and max len should less than 512 bytes
 * @param veri user verify function, first param is from remote user personal data, second is arg
 * @param arg the veri second param
 * @return int -1 for fail, 0 for success
 */
int set_user_verify(struct broadcast_handle *bc, char *data, int data_len, bool (*verify)(char *, void *), void *arg);

/**
 * @brief Set the device name object
 * 
 * @param bc 
 * @param name device name, less than 48 bytes
 * @return int -1 for fail, 0 for success
 */
int set_device_name(struct broadcast_handle *bc, char *name);

#endif  // _BROADCAST_H_
